<html>
        <head>
          <meta charset=utf-8>
          <title>mapgen</title>
          <style>
            body { margin: 0; }
            canvas { width: 100%; height: 100% }
          </style>

        <link rel="stylesheet" href="css/style.css">
        </head>
        <body>
          <script src="js/build/three.js"></script>
          <script src="js/THREE/controls/OrbitControls.js"></script>
          <script src="js/THREE/loaders/OBJLoader.js"></script>
          <script src="js/src/WorldGrid.js"></script>
          <script id="wood-vert" type="x-shader/x-vertex">
            varying vec3 vPos;
            varying vec3 unPos;
            varying vec3 vNor;
            varying vec3 light1;
            varying vec3 light2;
            varying vec3 vert;
            varying float camDepth;

            void main()	{
              vPos = vec3(modelViewMatrix * vec4(position, 1.0));
              unPos = vec3(modelMatrix * vec4(position, 1.0));
              vNor = normalMatrix * normal;
              light1 = vec3(viewMatrix * vec4(50, 7.2, 0, 1));
              light2 = vec3(viewMatrix * vec4(-50, 7.2, 0, 1));
              vert = vec3(viewMatrix * vec4(0, 50, 0, 1));

              vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
              gl_Position = projectionMatrix * mvPosition;
              camDepth = gl_Position.z / gl_Position.w;
            }
          </script>
          <script id="wood-frag" type="x-shader/x-fragment">
            // Noise functions from http://thebookofshaders.com/examples/

            #ifdef GL_ES
            precision mediump float;
            #endif

            varying vec3 vPos;
            varying vec3 unPos;
            varying vec3 vNor;
            varying vec3 light1;
            varying vec3 light2;
            varying vec3 vert;
            varying float camDepth;

            uniform vec2 u_resolution;
            uniform float u_time;
            uniform float groundDepth;

            float random (in vec2 st) {
              return fract(sin(dot(st.xy,
                                   vec2(12.9898,78.233)))*
                  43758.5453123);
            }

            // Based on Morgan McGuire @morgan3d
            // https://www.shadertoy.com/view/4dS3Wd
            float noise (in vec2 st) {
                vec2 i = floor(st);
                vec2 f = fract(st);

                // Four corners in 2D of a tile
                float a = random(i);
                float b = random(i + vec2(1.0, 0.0));
                float c = random(i + vec2(0.0, 1.0));
                float d = random(i + vec2(1.0, 1.0));

                vec2 u = f * f * (3.0 - 2.0 * f);

                return mix(a, b, u.x) +
                        (c - a)* u.y * (1.0 - u.x) +
                        (d - b) * u.x * u.y;
            }

            #define OCTAVES 6
            float fbm (in vec2 st) {
                // Initial values
                float value = 0.0;
                float amplitud = .5;
                float frequency = 0.;
                //
                // Loop of octaves
                for (int i = 0; i < OCTAVES; i++) {
                    value += amplitud * noise(st);
                    st *= 2.;
                    amplitud *= .5;
                }
                return value;
            }

            vec3 permute(vec3 x) {
              return mod((34.0 * x + 1.0) * x, 289.0);
            }

            // Cellular noise, returning F1 and F2 in a vec2.
            // Standard 3x3 search window for good F1 and F2 values
            vec2 cellular(vec2 P) {
              #define K 0.142857142857 // 1/7
              #define Ko 0.428571428571 // 3/7
              #define jitter 1.0 // Less gives more regular pattern
              vec2 Pi = mod(floor(P), 289.0);
              vec2 Pf = fract(P);
              vec3 oi = vec3(-1.0, 0.0, 1.0);
              vec3 of = vec3(-0.5, 0.5, 1.5);
              vec3 px = permute(Pi.x + oi);
              vec3 p = permute(px.x + Pi.y + oi); // p11, p12, p13
              vec3 ox = fract(p*K) - Ko;
              vec3 oy = mod(floor(p*K),7.0)*K - Ko;
              vec3 dx = Pf.x + 0.5 + jitter*ox;
              vec3 dy = Pf.y - of + jitter*oy;
              vec3 d1 = dx * dx + dy * dy; // d11, d12 and d13, squared
              p = permute(px.y + Pi.y + oi); // p21, p22, p23
              ox = fract(p*K) - Ko;
              oy = mod(floor(p*K),7.0)*K - Ko;
              dx = Pf.x - 0.5 + jitter*ox;
              dy = Pf.y - of + jitter*oy;
              vec3 d2 = dx * dx + dy * dy; // d21, d22 and d23, squared
              p = permute(px.z + Pi.y + oi); // p31, p32, p33
              ox = fract(p*K) - Ko;
              oy = mod(floor(p*K),7.0)*K - Ko;
              dx = Pf.x - 1.5 + jitter*ox;
              dy = Pf.y - of + jitter*oy;
              vec3 d3 = dx * dx + dy * dy; // d31, d32 and d33, squared
              // Sort out the two smallest distances (F1, F2)
              vec3 d1a = min(d1, d2);
              d2 = max(d1, d2); // Swap to keep candidates for F2
              d2 = min(d2, d3); // neither F1 nor F2 are now in d3
              d1 = min(d1a, d2); // F1 is now in d1
              d2 = max(d1a, d2); // Swap to keep candidates for F2
              d1.xy = (d1.x < d1.y) ? d1.xy : d1.yx; // Swap if smaller
              d1.xz = (d1.x < d1.z) ? d1.xz : d1.zx; // F1 is in d1.x
              d1.yz = min(d1.yz, d2.yz); // F2 is now not in d2.yz
              d1.y = min(d1.y, d1.z); // nor in  d1.z
              d1.y = min(d1.y, d2.x); // F2 is in d1.y, we're done.
              return sqrt(d1.xy) * 20.;
            }

            float edge(float v, float center, float edge0, float edge1) {
              return 1.0 - smoothstep(edge0, edge1, abs(v - center));
            }

            void main() {
                float height = (unPos.y + groundDepth - fract(unPos.y + groundDepth) + 1.65) * 0.25;

                vec2 st = unPos.yx / u_resolution.y;
                st.x *= u_resolution.x / u_resolution.y;
                float v0 = smoothstep(-1.0, 1.0, sin(st.x * 14.0 + fbm(st.xx * vec2(100.0, 2.0)) * 8.0));
                float v1 = random(st);
                float v2 = noise(st * vec2(200.0, 1.0)) - noise(st * vec2(1000.0, 64.0));

                vec3 col = vec3(210.0, 104.0, 76.0) / 255.0;
                col = mix(col, vec3(215, 162, 120) / 255.0, v0);
                col = mix(col, vec3(0.930,0.493,0.502), v1 * 0.5);
                col -= v2 * 0.05;
                vec3 rockCol = mix(vec3(210, 110, 93), vec3(215, 182, 150), fbm(vec2(height) / 0.0842)) / 255.0;
                col = rockCol * 0.7 + col * 0.25;

                vec2 worley = cellular(unPos.xz * 2.0 + vec2(42.12));
                float worleyDist = worley.y - worley.x;
                float fbmVal = fbm(unPos.xz / 1.2 + 2.0 * vec2(unPos.y)) + 0.5;
                worleyDist = clamp((max(worleyDist - .5, fbmVal * 1.1) - 0.95) * 16.0, 0.0, 1.0);

                float lightCol1 = dot(normalize(light1-vPos), normalize(vNor));
                float lightCol2 = dot(normalize(light2-vPos), normalize(vNor));
                float vertical = dot(normalize(vert - vPos), normalize(vNor));

                // Choose max between light sources or shadow color
                col *= max(vec3(0.35, 0.5, 0.9), max(lightCol1, lightCol2));

                // Add lighting from top
                col += vec3(0.85, 0.5, 0.25) * vertical;

                // Mix in cracks from worley noise
                col = mix(col, mix(col, vec3(0.9, 0.6, 0.5), 1.0 - worleyDist), vertical * vertical);

                // Add depth fog
                col = mix(col, vec3(0.43, 0.82 , 1.0), clamp(camDepth / 4. - 0.22, 0.0, 1.0) * 22.0);

                gl_FragColor = vec4(col, 1.0);
            }
          </script>

          <script id="grass-vert" type="x-shader/x-vertex">
            #ifdef GL_ES
            precision mediump float;
            #endif

            varying vec3 vPos;
            varying vec3 unPos;
            varying vec3 vNor;
            varying vec3 light1;
            varying vec3 light2;
            varying vec3 vert;

            uniform vec2 u_resolution;
            uniform float u_time;
            uniform float groundDepth;

            void main()	{
              vPos = vec3(modelViewMatrix * vec4(position, 1.0));
              unPos = vec3(modelMatrix * vec4(position, 1.0));
              vNor = normalMatrix * normal;
              light1 = vec3(viewMatrix * vec4(50, 7.2, 0, 1));
              light2 = vec3(viewMatrix * vec4(-50, 7.2, 0, 1));
              vert = vec3(viewMatrix * vec4(0, 50, 0, 1));

              float height = unPos.y + groundDepth - fract(unPos.y + groundDepth);
              float xOffset = sin(u_time) * sin(unPos.x) * cos(unPos.z);
              float zOffset = cos(u_time) * cos(unPos.x) * sin(unPos.z);
              vec3 offset = 0.64 * mix(vec3(0, 0, 0), vec3(xOffset, 0, zOffset), max(position.y - 0.55, 0.0));

              vec4 mvPosition = modelViewMatrix * vec4(position + offset, 1.0);
              gl_Position = projectionMatrix * mvPosition;
            }
          </script>
          <script id="grass-frag" type="x-shader/x-fragment">
            #ifdef GL_ES
            precision mediump float;
            #endif

            varying vec3 vPos;
            varying vec3 unPos;
            varying vec3 vNor;
            varying vec3 light1;
            varying vec3 light2;
            varying vec3 vert;

            uniform vec2 u_resolution;
            uniform float u_time;
            uniform float groundDepth;

            void main() {
                float height = (unPos.y + groundDepth - fract(unPos.y + groundDepth) + 1.65) * 0.25;

                vec3 col = vec3(0.188, 0.4, 0.345);

                float lightCol1 = dot(normalize(light1-vPos), normalize(vNor));
                float lightCol2 = dot(normalize(light2-vPos), normalize(vNor));
                float vertical = dot(normalize(vert - vPos), normalize(vNor));

                // Choose max between light sources or shadow color
                col *= max(max(lightCol1, lightCol2), 0.72);

                // Add lighting from top
                col += 0.25 * vec3(0.85, 0.5, 0.25) * vertical;

                gl_FragColor = vec4(col, 1.0);
            }
          </script>
          <script>
            var clock;
            var camera, scene, container, renderer;
            var world, dim, groundDepth;
            var plight, alight, cube;

            var mouse, controls, raycaster;
            var isShiftDown = false;

            var uniforms, date, start;
            var targetGeo, targetMat, targetMesh;
            var gridGeo, gridMat, groundMat, groundMat2;
            var uniforms, grassMat, rockMat, postMaterial;
            var colors = [];

            var objLoader;

            var voxels = [], tiles = [], decos = [];

            init();
            animate();

            function init() {
              clock = new THREE.Clock();

              scene = new THREE.Scene();
              container = new THREE.Group();
              dim = new THREE.Vector3(5,5,5);

              var viewSize = 12;
              var WIDTH = window.innerWidth;
              var HEIGHT = window.innerHeight;
              var aspectRatio = WIDTH / HEIGHT;
              //camera = new THREE.OrthographicCamera(-aspectRatio * viewSize / 2, aspectRatio * viewSize / 2, viewSize / 2, -viewSize / 2, -600, 600);
              camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
              scene.add(camera);
              camera.position.set(-Math.random()*12, Math.random()*8, -6);

              // Create a renderer and add it to the DOM.
              renderer = new THREE.WebGLRenderer({antialias:true});
              renderer.setSize(WIDTH, HEIGHT);
              // Lame fix to render 1px cracks from the tile shader
              renderer.setPixelRatio( window.devicePixelRatio * 1.001 );
              document.body.appendChild(renderer.domElement);

              mouse = new THREE.Vector2();
              controls = new THREE.OrbitControls(camera);
              raycaster = new THREE.Raycaster();

              // Create event listeners for mouse and keyboard inputs.
              document.addEventListener('mousemove', onDocumentMouseMove, false);
              document.addEventListener('mousedown', onDocumentMouseDown, false);
              document.addEventListener('mouseup', onDocumentMouseUp, false);
              document.addEventListener('keydown', onDocumentKeyDown, false);
              document.addEventListener('keyup', onDocumentKeyUp, false);
              // Create an event listener that resizes the renderer with the browser window.
              window.addEventListener('resize', function() {
                var WIDTH = window.innerWidth,
                    HEIGHT = window.innerHeight,
                    aspectRatio = WIDTH / HEIGHT;

                renderer.setSize(WIDTH, HEIGHT);
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
              });

              // Set the background color of the scene.
              var clearColor = new THREE.Color(0x7088dd);
              renderer.setClearColor(clearColor, 1);

              // Set up cube attributes
              gridMat = new THREE.MeshBasicMaterial( { color: 0xffffff } );
              gridMat.visible = false;
              gridGeo = new THREE.BoxGeometry(1, 1, 1);
              gridGeo.computeFaceNormals();

              groundMat = new THREE.MeshLambertMaterial( {color:0xaa7755} );
              groundMat2 = new THREE.MeshLambertMaterial( {color:0x845533} );
              //grassMat = new THREE.MeshLambertMaterial( {color:0x306658} );
              rockMat = new THREE.MeshLambertMaterial( {color:0xbb7766 } );

               // Add lights to the scene
              light1 = new THREE.DirectionalLight(0xffffff, 1, 100);
              light1.position.set(-8,7.2,0);
              light2 = new THREE.DirectionalLight(0xffffff, 1, 100);
              light2.position.set(8,7.2,0);
              light3 = new THREE.DirectionalLight(0x6688ff, .85, 100);
              light3.position.set(0,0,8);
              light4 = new THREE.DirectionalLight(0x6688ff, .85, 100);
              light4.position.set(0,0,-8);
              scene.add(light1);
              scene.add(light2);
              scene.add(light3);
              scene.add(light4);

              groundDepth = 0.25;

              uniforms = {
                u_resolution: { type: "v2", value: new THREE.Vector2(42, 42) },
                u_time: { value: 1.0 },
                groundDepth: groundDepth
              };

              postMaterial = new THREE.ShaderMaterial( {
                uniforms: uniforms,
                vertexShader: document.querySelector( '#wood-vert' ).textContent,
                fragmentShader: document.querySelector( '#wood-frag' ).textContent
              });

              grassMat = new THREE.ShaderMaterial( {
                uniforms: uniforms,
                vertexShader: document.querySelector( '#grass-vert' ).textContent,
                fragmentShader: document.querySelector( '#grass-frag' ).textContent
              });

              // Add rollover geometry to the scene
              targetGeo = new THREE.PlaneGeometry(.8, .8);
              targetMat = new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 0.25, transparent: true, side: THREE.DoubleSide } );
              targetMat.depthTest = false;
              targetMesh = new THREE.Mesh(targetGeo, targetMat);
              targetMesh.visible = false;
              container.add(targetMesh);

              // Load rock tiles and ground platform
              createPalettes(dim.y);
              objLoader = new THREE.OBJLoader();
              loadTiles();
              loadGround(dim);
              container.position.y += groundDepth/2;
              container.position.x -= dim.x/2 - 0.5;
              container.position.y -= dim.y/2 - 0.5;
              container.position.z -= dim.z/2 - 0.5;
              scene.add(container);
            }

            // Wrapper function to deal with unwanted async loading
            function getObjPromise(url) {
              return new Promise(resolve => {
                objLoader.load(url, resolve);
              });
            }

            // Loads tile .obj's and calls loadWorld() when finished
            function loadTiles() {
              for(var n = 0; n < 10; ++n)
              {
                tiles[n] = [];
              }

              var promises = new Array();
              var i = 0;
              // Import regular block
              promises[i++] = getObjPromise('obj/flat.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[0][0] = object;
                tiles[0][1] = object;
              });
              promises[i++] = getObjPromise('obj/flat2.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[0][2] = object;
              });
              promises[i++] = getObjPromise('obj/flat3.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[0][3] = object;
              });
              promises[i++] = getObjPromise('obj/flatFat.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[1][0] = object;
              });

              // Import corner block
              promises[i++] = getObjPromise('obj/corner.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[2][0] = object;
                tiles[2][1] = object;
              });
              promises[i++] = getObjPromise('obj/corner2.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[2][2] = object;
              });
              promises[i++] = getObjPromise('obj/flatFat.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[3][0] = object;
              });

              // TODO: TWO NEIGHBORS LINE
              promises[i++] = getObjPromise('obj/flat.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[4][0] = object;
                tiles[5][0] = object;
              });

              // Import one block
              promises[i++] =  getObjPromise('obj/one.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[6][0] = object;
              });
              promises[i++] =  getObjPromise('obj/one2.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[6][1] = object;
              });
              promises[i++] = getObjPromise('obj/flatFat.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[7][0] = object;
              });

              // Import solo block
              promises[i++] = getObjPromise('obj/solo.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[8][0] = object;
              });
              promises[i++] = getObjPromise('obj/solo2.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[8][1] = object;
              });
              promises[i++] = getObjPromise('obj/soloFat.obj').then(object => {
                object.children[0].material = postMaterial;
                tiles[9][0] = object;
              });

              promises[i++] = getObjPromise('obj/grass3.obj').then(object => {
                object.children[0].material = grassMat;
                decos[0] = object;
              });

              promises[i++] = getObjPromise('obj/grass3.obj').then(object => {
                object.children[0].material = grassMat;
                decos[1] = object;
              });

              promises[i++] = getObjPromise('obj/rock2.obj').then(object => {
                object.children[0].material = rockMat;
                decos[2] = object;
              });

              Promise.all(promises).then(() => {
                loadWorld(dim);
              });
            }

            // Loads tile data from WorldGrid
            function loadWorld(dim) {
              // Draw from world
              world = new WorldGrid(dim.x, dim.y, dim.z);
              for(var x = 0; x < world.x; x++) {
                for(var y = -1; y < world.y; y++) {
                  for(var z = 0; z < world.z; z++) {
                    // Add invisible voxels for selection on ground
                    if(y < 0) {
                      var pos = new THREE.Vector3(x,y,z);
                      var voxel = new THREE.Mesh(gridGeo, gridMat);
                      voxel.position.copy(pos);
                      voxel.name = pos.toArray().toString();
                      container.add(voxel);
                      voxels.push(voxel);
                      if(world.grid[x][y+1][z] == 0) {
                          spawnDeco(pos);
                      }
                    }
                    else {
                      if(world.grid[x][y][z] == 1) {
                        var data = world.getBlockType(x, y, z);
                        // Place voxel
                        var pos = new THREE.Vector3(x,y,z);
                        var voxel = new THREE.Mesh(gridGeo, gridMat);
                        voxel.position.copy(pos);
                        voxel.name = pos.toArray().toString();
                        container.add(voxel);
                        voxels.push(voxel);

                        // Place tile
                        var index = data.type;
                        tile = tiles[index][Math.round(Math.pow(Math.random(), 3) * (tiles[index].length - 1))].clone();
                        tile.scale.x += Math.random() / 1000;
                        tile.scale.z += Math.random() / 1000;
                        //tile.children[0].material = colors[y];
                        tile.position.copy(pos);
                        tile.rotation.set(0, data.rot, 0);
                        tile.name = pos.toArray().toString().concat("t");

                        container.add(tile);
                        if(y == dim.y - 1) {
                          spawnDeco(pos);
                        }
                        else if(world.grid[x][y+1][z] == 0) {
                          spawnDeco(pos);
                        }
                      }
                    }
                  }
                }
              }
            }

            // Creates a ground platform
            function loadGround(dim) {
              var y = -0.5 - groundDepth/2;
              ground = new THREE.Mesh(gridGeo, groundMat);
              ground.scale.set(dim.x * 1.2, groundDepth, dim.z * 1.2);
              ground.position.set(dim.x/2 - 0.5, y, dim.z/2 - 0.5);

              ground2 = new THREE.Mesh(gridGeo, groundMat2);
              ground2.scale.set(dim.x * 1.198, groundDepth * 3, dim.z * 1.198);
              ground2.position.set(dim.x/2 - 0.5, -3, dim.z/2 - 0.5);
              ground2.position.x -= dim.x/2 - 0.5;
              ground2.position.z -= dim.z/2 - 0.5;

              var surfaceGeo = new THREE.BoxGeometry(1,1,1,3,3,3);
              for(var i = 34; i < 38; i++) {
                var vert = surfaceGeo.vertices[i];
                vert.y += Math.random() * 0.65;
              }
              bumps = new THREE.Mesh(surfaceGeo, groundMat);
              bumps.scale.set(4,groundDepth,4);
              bumps.position.set(dim.x/2 - 0.5,y,dim.z/2 - 0.5);

              container.add(ground);
              scene.add(ground2);
              container.add(bumps);
            }

            // Creates a color for each layer of tiles
            function createPalettes(n) {
              var gray = new THREE.Color(0xd79588);
              for(var i = 0; i < n; i++) {
                var alpha = Math.pow(Math.random(), 2);
                var col = new THREE.Color(0xd26e5d).lerp(gray, alpha);
                var mat = new THREE.MeshLambertMaterial( {color:col} );
                colors[i] = mat;
              }
            }

            // Updates tile visuals of blocks surrounding tilePos
            function updateNeighbors(tilePos, rec) {
              var xmin = Math.max(tilePos.x - 1, 0), xmax = Math.min(tilePos.x + 1, world.x - 1);
              var ymin = Math.max(tilePos.y - 1, 0), ymax = Math.min(tilePos.y + 1, world.y - 1);
              var zmin = Math.max(tilePos.z - 1, 0), zmax = Math.min(tilePos.z + 1, world.z - 1);
              for(var x = xmin; x <= xmax; x++) {
                for(var y = ymin; y <= ymax; y++) {
                  for(var z = zmin; z <= zmax; z++) {
                    if(world.grid[x][y][z] != 0 && world.getBlockType(x, y, z).type != world.types[x][y][z]) {
                      var checkPos = new THREE.Vector3(x, y, z);
                      // Remove outdated tile
                      container.remove(container.getObjectByName(checkPos.toArray().toString().concat("t")));
                      // Add updated tile
                      var data = world.getBlockType(x,y,z);
                      var index = data.type;
                      var newTile = tiles[index][Math.round(Math.pow(Math.random(), 3)) * (tiles[index].length - 1)].clone();
                      //newTile.children[0].material = colors[y];
                      newTile.position.copy(checkPos);
                      newTile.rotation.set(0, data.rot, 0);
                      newTile.name = checkPos.toArray().toString().concat("t");
                      container.add(newTile);
                      // Update world data
                      world.types[x][y][z] = world.getBlockType(x,y,z).type;
                    }
                  }
                }
              }
              if(rec) {
                updateNeighbors(tilePos, false);
              }
            }

            function onDocumentMouseMove(event) {
              event.preventDefault();
              mouse.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1);
              render();
              updateTarget();
            }

            function onDocumentMouseDown(event) {
              event.preventDefault();
      				mouse.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1);
              var intersects = raycaster.intersectObjects(voxels);
              if(intersects.length > 0)
              {
                controls.enabled = false; // Disable OrbitControls when user clicks on tiles
                var intersect = intersects[0];
                var pos = intersect.object.position.clone();
                var nor = intersect.face.normal;

                var voxel = new THREE.Mesh(gridGeo, gridMat);
                // Find maximum component of face normal
                var maxComp = getMaxComp(nor);

                if(isShiftDown){
                  if(pos.y > -1) {
                    container.remove(container.getObjectByName(pos.toArray().toString().concat("t")));
                    container.remove(container.getObjectByName(pos.toArray().toString()));
                    world.grid[pos.x][pos.y][pos.z] = 0;
                    voxels.splice(voxels.indexOf(intersect.object), 1);
                    updateNeighbors(pos, true);
                  }

                  // Handle decorative items
                  container.remove(container.getObjectByName(pos.toArray().toString().concat("d")));
                  if(pos.y > 0) {
                    if(world.grid[pos.x][pos.y-1][pos.z] == 1) {
                    var belowPos = new THREE.Vector3(pos.x, pos.y - 1, pos.z);
                    spawnDeco(belowPos);
                    }
                  }
                  else {
                    var belowPos = new THREE.Vector3(pos.x, pos.y - 1, pos.z);
                    spawnDeco(belowPos);
                  }
                }
                else {
                  var placed = false;
                  var tile;
                  // Update world.grid if click is within world bounds
                  switch(maxComp) {
                    case 0:
                      if(pos.x + nor.x < dim.x && pos.x + nor.x >= 0) {
                        placed = true;
                      }
                      break;
                    case 1:
                      if(pos.y < 0 && nor.y > 0) { // Place block on ground
                        pos.y = -1;
                        placed = true;
                      }
                      else if (pos.y + nor.y < dim.y && pos.y + nor.y >= 0) {
                        placed = true;
                      }
                      break;
                    case 2:
                      if(pos.z + nor.z < dim.z && pos.z + nor.z >= 0) {
                        placed = true;
                      }
                      break;
                  }
                  if(placed) {
                    var tilePos = new THREE.Vector3(pos.x + nor.x, pos.y + nor.y, pos.z + nor.z);
                    var data = world.getBlockType(tilePos.x, tilePos.y, tilePos.z);
                    // Update world data
                    world.grid[tilePos.x][tilePos.y][tilePos.z] = 1;
                    world.types[tilePos.x][tilePos.y][tilePos.z] = data.type;
                    // Place voxel
                    voxel.position.copy(pos).add(nor);
                    voxel.name = tilePos.toArray().toString();
                    container.add(voxel);
                    voxels.push(voxel);
                    // Place tile
                    var index = data.type;
                    tile = tiles[index][Math.round(Math.pow(Math.random(), 3) * (tiles[index].length - 1))].clone();
                    //tile.children[0].material = colors[tilePos.y];
                    tile.position.copy(tilePos);
                    tile.rotation.set(0, data.rot, 0);
                    tile.name = tilePos.toArray().toString().concat("t");
                    container.add(tile);

                    updateNeighbors(tilePos, true);

                    // Handle decorative items
                    if(tilePos.y == dim.y - 1) {
                      var abovePos = new THREE.Vector3(tilePos.x, tilePos.y, tilePos.z);
                      spawnDeco(abovePos);
                    }
                    else if(world.grid[tilePos.x][tilePos.y + 1][tilePos.z] == 0) {
                      var abovePos = new THREE.Vector3(tilePos.x, tilePos.y, tilePos.z);
                      spawnDeco(abovePos);
                    }
                    var belowPos = new THREE.Vector3(tilePos.x, tilePos.y - 1, tilePos.z);
                    container.remove(container.getObjectByName(belowPos.toArray().toString().concat("d")));
                  }
                }
              }
              render();
              updateTarget();
            }

            function onDocumentMouseUp(event) {
              controls.enabled = true;
            }

            function onDocumentKeyDown(event) {
              switch( event.keyCode ) {
                case 16: isShiftDown = true;
                break;
              }
            }

            function onDocumentKeyUp(event) {
              switch ( event.keyCode ) {
                case 16: isShiftDown = false;
                break;
              }
            }

            // Helper function for dealing with surface normals
            function getMaxComp(vec3) {
              var absVec = new THREE.Vector3(Math.abs(vec3.x), Math.abs(vec3.y), Math.abs(vec3.z));
              if(absVec.x > absVec.y && absVec.x > absVec.z) {
                  return 0;
                }
                else if(absVec.y > absVec.x && absVec.y > absVec.z) {
                  return 1;
                }
                return 2;
            }

            // Updates the location of the target square
            function updateTarget() {
              raycaster.setFromCamera(mouse, camera);
              var intersects = raycaster.intersectObjects(voxels);
              if(intersects.length > 0) {
                targetMesh.visible = true;
                var intersect = intersects[0];
                var pos = intersect.object.position;
                var nor = intersect.face.normal;
                targetMesh.position.copy(pos);
                var maxComp = getMaxComp(nor);

                // Check if selection is on the ground
                if(pos.y < 0) {
                  if(nor.y <= 0) {
                    targetMesh.visible = false;
                  }
                  targetMesh.rotation.set(Math.PI/2, 0, 0);
                  targetMesh.position.y = -.499;
                  return;
                }

                switch(maxComp) {
                  case 0:
                    targetMesh.rotation.set(0, Math.PI/2, 0);
                    targetMesh.position.x += nor.x / 1.9;
                    break;
                  case 1:
                    targetMesh.rotation.set(Math.PI/2, 0, 0);
                    targetMesh.position.y += nor.y / 1.9;
                    break;
                  case 2:
                    targetMesh.rotation.set(0, 0, Math.PI/2);
                    targetMesh.position.z += nor.z / 1.9;
                    break;
                }
              }
              else {
                targetMesh.visible = false;
              }
            }

            // Randomly spawns decorative items at given position
            function spawnDeco(vec3) {
              if(Math.random() > 0.75) {
                var index = Math.round(Math.random() * (decos.length - 1));
                var deco = decos[index].clone();
                deco.position.copy(vec3);
                deco.name = vec3.toArray().toString().concat("d");
                var rotations = [Math.PI/4, 3*Math.PI/4, 5*Math.PI/4, 7*Math.PI/4];
                deco.rotation.set(0, rotations[Math.round(Math.random() * 3)], 0);
                container.add(deco);
              }
            }

            function render() {
              uniforms.u_time.value += clock.getDelta();
              renderer.render( scene, camera );
            }

            function animate() {
              requestAnimationFrame(animate);
              render();
            }

            animate();
            controls.update();
          </script>
        </body>
</html>